package com.gitee.yannzh.rune.support.crud.register;

import com.gitee.yannzh.rune.core.base.pojo.Param;
import com.gitee.yannzh.rune.support.crud.annotation.Api;
import com.gitee.yannzh.rune.support.crud.annotation.Rune;
import com.gitee.yannzh.rune.support.crud.base.CrudControllerProxy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.stream.Stream;

/**
 * @author Yann
 * @date 2024/9/21 23:46
 * @description 动态
 * @since 1.0.0
 */
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings({"rawtypes", "unchecked"})
public class AutoCrudHandlerRegister implements ApplicationListener<ApplicationReadyEvent> {

    private final RequestMappingHandlerMapping handlerMapping;

    private final ApplicationContext applicationContext;


    @Override
    public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {

        log.info("[rune] Start registering CRUD with RuneApi annotation");
        applicationContext.getBeansWithAnnotation(Rune.class).forEach(
                (beanName, bean) -> {
                    Class<?> beanClass = AopProxyUtils.ultimateTargetClass(bean);
                    Rune rune = beanClass.getAnnotation(Rune.class);
                    CrudControllerProxy handler = createCrudControllerProxy(beanClass, rune);
                    String prefix = beanClass.getAnnotation(RequestMapping.class).value()[0].replace("/","");
                    for (Method method : CrudControllerProxy.class.getMethods()) {
                        if (method.isAnnotationPresent(Api.class)) {
                            Api api = method.getAnnotation(Api.class);
                            if (Stream.of(rune.api()).toList().contains(api.value())) {
                                RequestMappingInfo mapping = RequestMappingInfo
                                        .paths(String.format("/%s/%s", prefix, api.value()))
                                        .options(initConfiguration(handlerMapping))
                                        .methods(api.method())
                                        .build();
                                handlerMapping.registerMapping(mapping, handler, method);
                            }

                        }
                    }
                }
        );
        log.info("[rune] The CRUD API is success registered");


    }

    private CrudControllerProxy createCrudControllerProxy(Class<?> beanClass, Rune rune) {
        CrudControllerProxy handler = new CrudControllerProxy(beanClass, rune.entityType(), rune.paramType());

        Class<? extends Param> saveType = rune.saveType();
        if (saveType != Param.class) {
            handler.setSaveParamType(saveType);
        }

        Class<? extends Param> updateType = rune.updateType();
        if (updateType != Param.class) {
            handler.setUpdateParamType(updateType);
        }

        Class<? extends Param> queryType = rune.queryType();
        if (queryType != Param.class) {
            handler.setQueryParamType(queryType);
        }
        return handler;
    }

    /**
     * 核心配置类，因为`RequestMappingHandlerMapping`中config属性未提供公共方法，所以需要自行构建
     */
    private RequestMappingInfo.BuilderConfiguration initConfiguration(RequestMappingHandlerMapping handlerMapping) {
        RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
        config.setTrailingSlashMatch(handlerMapping.useTrailingSlashMatch());
        config.setContentNegotiationManager(handlerMapping.getContentNegotiationManager());
        if (handlerMapping.getPatternParser() != null) {
            config.setPatternParser(handlerMapping.getPatternParser());
            Assert.isTrue(!handlerMapping.useSuffixPatternMatch() && !handlerMapping.useRegisteredSuffixPatternMatch(),
                    "Suffix pattern matching not supported with PathPatternParser.");
        } else {
            config.setSuffixPatternMatch(handlerMapping.useSuffixPatternMatch());
            config.setRegisteredSuffixPatternMatch(handlerMapping.useRegisteredSuffixPatternMatch());
            config.setPathMatcher(handlerMapping.getPathMatcher());
        }
        return config;
    }
}
